package ccomp.parser_hw;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.math.BigInteger;
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.ListIterator;
import java.util.Map;
import java.util.Stack;

import SFE.Compiler.AnyType;
import SFE.Compiler.ArrayType;
import SFE.Compiler.AssertZeroStatement;
import SFE.Compiler.BitString;
import SFE.Compiler.BitStringHelper;
import SFE.Compiler.BitStringHelper.BitStringStoreTarget;
import SFE.Compiler.BlockStatement;
import SFE.Compiler.BooleanConstant;
import SFE.Compiler.BooleanType;
import SFE.Compiler.BusType;
import SFE.Compiler.CompileTimeOperator;
import SFE.Compiler.ConstExpression;
import SFE.Compiler.Consts;
import SFE.Compiler.ExoComputeStatement;
import SFE.Compiler.RamGetEnhancedStatement;
import SFE.Compiler.RamPutEnhancedStatement;
import SFE.Compiler.FloatConstant;
import SFE.Compiler.Function;
import SFE.Compiler.GenericGetStatement;
import SFE.Compiler.HashFreeStatement;
import SFE.Compiler.HashGetStatement;
import SFE.Compiler.HashPutStatement;
import SFE.Compiler.InputStatement;
import SFE.Compiler.IntConstant;
import SFE.Compiler.IntType;
import SFE.Compiler.LvalExpression;
import SFE.Compiler.OperationExpression;
import SFE.Compiler.Pointer;
import SFE.Compiler.PointerType;
import SFE.Compiler.PrintfStatement;
import SFE.Compiler.ProtoAssignmentStatement;
import SFE.Compiler.RamGetStatement;
import SFE.Compiler.RamPutStatement;
import SFE.Compiler.RestrictedSignedIntType;
import SFE.Compiler.RestrictedUnsignedIntType;
import SFE.Compiler.SLPTReduction;
import SFE.Compiler.StatementBuffer;
import SFE.Compiler.StructType;
import SFE.Compiler.Type;
import SFE.Compiler.TypeHeirarchy;
import SFE.Compiler.VarLvalue;
import SFE.Compiler.Variable;
import SFE.Compiler.Operators.ArrayAccessOperator;
import SFE.Compiler.Operators.BitwiseOperator;
import SFE.Compiler.Operators.CStyleCast;
import SFE.Compiler.Operators.DivisionOperator;
import SFE.Compiler.Operators.GetPointerOperator;
import SFE.Compiler.Operators.MinusOperator;
import SFE.Compiler.Operators.NotEqualOperator;
import SFE.Compiler.Operators.Operator;
import SFE.Compiler.Operators.PlusOperator;
import SFE.Compiler.Operators.PointerAccessOperator;
import SFE.Compiler.Operators.StructAccessOperator;
import SFE.Compiler.Operators.TimesOperator;
import SFE.Compiler.Operators.UnaryMinusOperator;
import SFE.Compiler.Operators.UnaryPlusOperator;
import ccomp.CBuiltinFunctions;
import ccomp.CMemoryMap;
import ccomp.parser.CParser;
import ccomp.parser.CScanner;
import ccomp.parser_hw.HLL_ASTNodes.AssignmentStatement;
import ccomp.parser_hw.HLL_ASTNodes.BinaryOpExpression;
import ccomp.parser_hw.HLL_ASTNodes.CastExpression;
import ccomp.parser_hw.HLL_ASTNodes.CompoundStatement;
import ccomp.parser_hw.HLL_ASTNodes.ConditionalExpression;
import ccomp.parser_hw.HLL_ASTNodes.Constant;
import ccomp.parser_hw.HLL_ASTNodes.Declaration;
import ccomp.parser_hw.HLL_ASTNodes.Expression;
import ccomp.parser_hw.HLL_ASTNodes.FunctionCall;
import ccomp.parser_hw.HLL_ASTNodes.FunctionDefinition;
import ccomp.parser_hw.HLL_ASTNodes.Identifier;
import ccomp.parser_hw.HLL_ASTNodes.IfStatement;
import ccomp.parser_hw.HLL_ASTNodes.LoopStatement;
import ccomp.parser_hw.HLL_ASTNodes.MultiExpression;
import ccomp.parser_hw.HLL_ASTNodes.MultiVariableDeclaration;
import ccomp.parser_hw.HLL_ASTNodes.Program;
import ccomp.parser_hw.HLL_ASTNodes.ReturnStatement;
import ccomp.parser_hw.HLL_ASTNodes.Statement;
import ccomp.parser_hw.HLL_ASTNodes.StructTypeSpecification;
import ccomp.parser_hw.HLL_ASTNodes.TypeDefSpecification;
import ccomp.parser_hw.HLL_ASTNodes.TypeSpecification;
import ccomp.parser_hw.HLL_ASTNodes.UnaryOpExpression;
import ccomp.parser_hw.HLL_ASTNodes.VariableDeclaration;

/**
 * NOTE: You will get compile errors unless you also include on your build path
 * the java files in /gensrc, which are generated by invoking the compiler
 * generators beaver and flex. The shell script build-ccomp.sh does this for
 * you, as does invoking make from the /frontend folder.
 */
public class CCompiler {
	private static String LEXICAL_RESET_SEPARATOR = "#"; // Can't use $ or : or
															// !
	private static String UNNAMED_BLOCK_SCOPE_NAME = "";

	private Reader file;

	public CCompiler(Reader file) {
		this(file, true);
	}

	public CCompiler(Reader file, boolean cstdarithtruncate) {
		this.file = file;
		typetable = new HashMap();
		functable = new HashMap();
		functable2 = new HashMap();
		this.cstdarithtruncate = cstdarithtruncate;

		builtinTypes();
		builtinFunctions();
	}

	public static void main_(String[] args) throws IOException {
		SFE.Compiler.Program p = new SFE.Compiler.Program();
		CCompiler cc = new CCompiler(
				new BufferedReader(new FileReader(args[0])));
		cc.compileProgram(p);
		System.out.println("Parse successful.");
	}

	// The "value" returned when returning from a void method. Conveniently also
	// works for the default return value
	// for default-int functions.
	private SFE.Compiler.Expression VoidRetVal = new SFE.Compiler.UnaryOpExpression(
			new UnaryPlusOperator(), IntConstant.valueOf(0));
	/**
	 * If true, implicit truncating casts are added during arithmetic and
	 * assignment of numeric types, as per the ANSI C standard.
	 * 
	 * If false, no truncating casts are added unless they are explicit (i.e.
	 * (uint32_t)0xF00000000L will truncate)
	 */
	private boolean cstdarithtruncate;
	private Map<String, Type> typetable;
	private Map<String, FunctionDefinition> functable;
	private Map<String, Function> functable2;

	public Collection<String> getDefinedTypeNames() {
		return new HashSet<String>(typetable.keySet());
	}

	public void compileProgram(SFE.Compiler.Program program) {
		CParser cp = new CParser();
		Program p = null;
		try {
			p = (Program) cp.parse(new CScanner(file));
		} catch (IOException e) {
			throw new RuntimeException(e);
		} catch (beaver.Parser.Exception e) {
			System.out.println("Error parsing .c file: "
					+ e.getLocalizedMessage());
			// e.printStackTrace();
		}

		SFE.Compiler.Function main = new Function("compute", new AnyType());
		main.setOutput(true);
		main.addStatement(new CMainStatement(p));

		program.addFunction(main);

		// setProgram_(program, p);
	}

	private static class CLexicalScope {
		private Stack<String> scopeNames = new Stack();
		private Stack<HashSet<String>> scope = new Stack();

		/**
		 * If lexicalReset is true, variable lookups that try to read from a
		 * parent scope will be disallowed, and will jump to lookup in the
		 * global variables.
		 * 
		 * I.e. lexicalReset is true when inlining a function call, to ensure
		 * static scoping.
		 */
		public void pushScope(String name, boolean lexicalReset) {
			scope.push(new HashSet());
			if (lexicalReset) {
				name = LEXICAL_RESET_SEPARATOR + name;
			}
			scopeNames.push(name);
		}

		public void pushScope(String name) {
			pushScope(name, false);
		}

		public void popScope() {
			scope.pop();
			scopeNames.pop();
		}

		public String getID(String name) {
			// Search backwards through this lexical scope, and return the
			// prefix-prepended name.
			// If we get to the bottom, return the name as-is (C global var)

			if (!scope.isEmpty()) {
				boolean found = false;
				for (ListIterator itr = scope.listIterator(scope.size()), itr2 = scopeNames
						.listIterator(scope.size()); itr.hasPrevious();) {
					HashSet<String> got = (HashSet<String>) itr.previous();
					String prefix = (String) itr2.previous();
					if (got.contains(name)) {
						found = true; // Start prepending prefixes.
					}
					if (found) {
						name = prefix + "$" + name;
					}
					if (!found && prefix.startsWith("LEXICAL_RESET_SEPARATOR")) {
						break; // Don't look below lexical_reset: prefixes.
					}
				}
			}

			// Global variable.
			return name;
		}

		private String getAddID(String name) {
			StringBuffer prefix = new StringBuffer();
			for (String k : scopeNames) {
				prefix.append(k);
				prefix.append("$");
			}
			if (prefix.length() == 0) {
				return name;
			}
			return prefix + name;
		}

		public LvalExpression addVar(String string, Type cType, boolean isOutput) {
			if (scope.peek().contains(string)) {
				throw new RuntimeException("Variable declared twice: " + string);
			}
			scope.peek().add(string);

			String name = getAddID(string);
			LvalExpression alreadyExists = Function.getVars().getVar(name);
			if (alreadyExists == null) {
				alreadyExists = Function.getVars().addVar(getAddID(string),
						cType, false, isOutput);
			}
			return alreadyExists;
		}
	}

	private void addGlobalVariable(CLexicalScope scope, VariableDeclaration q,
			StatementBuffer sb) {
		// Check if already defined
		if (Function.getVars().getVar(q.name.identifier) != null) {
			throw new RuntimeException("Global variable declared twice: "
					+ q.name.identifier);
		}
		// Special: Global variable named __stdout_buffer is an output variable.
		boolean isOutput = false;
		boolean isInput = false;
		if (q.name.identifier.equals("__stdout_buffer")) {
			isOutput = true;
		} else if (q.name.identifier.equals("__stdin_buffer")) {
			isInput = true;
		}

		LvalExpression gvar = Function.getVars().addVar(q.name.identifier,
				getCType(q.type, q.name), false, isOutput);
		gvar.allocateStackAddress();
		if (isInput) {
			addStatement(new InputStatement(gvar), sb);
		} else {
			if (q.initializer == null) {
				// Default int setting for global variables shouldn't surprise
				// anyone,
				// because programmers should know
				// not to rely on the initialization of global variables.
				// System.err.println("Warning - initializing global variable "+q.name+" with value 0");
				// throw new
				// RuntimeException("Global variables must be declared with an initial value: "+q.name);
			}
			addStatement(asId(gvar, IntConstant.ZERO), sb);
			// Initializer.
			if (q.initializer != null) {
				addInitializerStatement(gvar, scope, q.initializer, sb);

				// Global variables with the type specifier "const" are added to
				// the
				// consts list, if possible
				if (q.type.spec.contains("const")) {
					// gvar is out of date
					gvar = gvar.changeReference(Function.getVars());
					ConstExpression ce = ConstExpression
							.toConstExpression(gvar);
					if (ce != null) {
						Consts.addConst(gvar.getName(), ce, false);
					}
				}
			}
		}
	}

	private void addInitializerStatement(LvalExpression gvar,
			CLexicalScope scope, Expression initializer, StatementBuffer sb) {
		if (initializer instanceof MultiExpression) {
			MultiExpression me = (MultiExpression) initializer;
			if (me.size() > gvar.size()) {
				throw new RuntimeException("Cannot assign array initializer "
						+ me + " to lval " + gvar);
			}
			for (int i = 0; i < me.size(); i++) {
				/*
				 * addStatement(new ProtoAssignmentStatement( new
				 * SFE.Compiler.BinaryOpExpression(new ArrayAccessOperator(),
				 * gvar, IntConstant.valueOf(i)), new
				 * SFE.Compiler.UnaryOpExpression(new UnaryPlusOperator(),
				 * getExpr(scope, me.get(i), sb)), null ), sb);
				 */
				// Recursive method to allow multidimensional array initializers
				if (gvar.getDeclaredType() instanceof ArrayType) {
					LvalExpression component = new ArrayAccessOperator()
							.resolve(gvar, IntConstant.valueOf(i));
					addInitializerStatement(component, scope, me.get(i), sb);
				} else if (gvar.getDeclaredType() instanceof StructType) {
					StructType struct = (StructType) gvar.getDeclaredType();
					String fieldname = struct.getFields().get(i);
					LvalExpression component = new StructAccessOperator(
							fieldname).resolve(gvar);
					addInitializerStatement(component, scope, me.get(i), sb);
				} else {
					throw new RuntimeException("Cannot assign multiexpression "
							+ me + " to lvalue " + gvar);
				}
			}
		} else {
			addStatement(asId(gvar, getExpr(scope, initializer, sb)), sb);
		}
	}

	private int malloc_num = CMemoryMap.HEAP; // start of heap memory

	private static class Malloc {
		public LvalExpression data;
		public Pointer ptr;
	}

	private Malloc malloc(Type pointedToType, boolean isOutput) {
		if (pointedToType instanceof ArrayType) {
			throw new RuntimeException("I don't know how to malloc an array");
		}
		Malloc toRet = new Malloc();
		int num = malloc_num;
		toRet.data = Function.getVars().addVar("__malloc" + num, pointedToType,
				false, isOutput);
		toRet.data.allocateHeapAddress();
		toRet.ptr = new Pointer(pointedToType,
				new LvalExpression[] { toRet.data }, num, num);

		// Advance
		malloc_num++;

		// Return
		return toRet;
	}

	private class CMainStatement extends SFE.Compiler.Statement {
		private Program program;

		public CMainStatement(Program program) {
			this.program = program;
		}

		public SFE.Compiler.Statement toSLPTCircuit(Object obj) {
			return this; // Defer all processing.
		}

		public SFE.Compiler.Statement duplicate() {
			throw new RuntimeException("Not yet implemented");
		}

		public void toAssignmentStatements(StatementBuffer sb) {
			CLexicalScope main_s = new CLexicalScope();
			for (Object q : program) {
				if (q instanceof FunctionDefinition) {
					addFunction((FunctionDefinition) q);
				} else if (q instanceof StructTypeSpecification) {
					addStructType((StructTypeSpecification) q);
				} else if (q instanceof VariableDeclaration) {
					VariableDeclaration q2 = (VariableDeclaration) q;
					if (q2.type instanceof TypeDefSpecification) { // Strangely,
																	// typedefs
																	// parse
																	// through
																	// as
																	// variable
																	// declarations.
						TypeDefSpecification typeDef = (TypeDefSpecification) q2.type;
						addTypeDef(getCType(typeDef.type, q2.name), q2.name);
					} else {
						addGlobalVariable(main_s, q2, sb);
					}
				}
			}
			String nameOfMain = "compute";
			// We will only add one function object to the created program.
			FunctionDefinition main_d = functable.get(nameOfMain);

			// Create new lexical scope for function call
			main_s.pushScope(nameOfMain, true);

			// Get the input (should be first arg of main, a pointer.)
			VariableDeclaration input = null;
			VariableDeclaration output = null;

			// Create the input variables (X), and assign inputstatements (X <-
			// input)
			for (VariableDeclaration v : main_d.functionNameAndArgs.args) {
				if (v.name.identifier.equals("input")) {
					input = v;
				} else if (v.name.identifier.equals("output")) {
					output = v;
				} else {
					throw new RuntimeException(
							"Currently, the main method must take a pointer to struct called input, and a pointer to struct called output, and no other arguments");
				}
			}
			if (input == null || output == null) {
				throw new RuntimeException(
						"Currently, the main method must take a pointer to struct called input, and a pointer to struct called output, and nothing else");
			}

			// Add the input variable (X)
			PointerType inPtr_t = ((PointerType) getCType(input.type,
					input.name));
			// Add the variable for the data of Xptr
			Malloc Xmalloc = malloc(inPtr_t.getPointedToType(), false);

			// Fill the input with input statements
			addStatement(new InputStatement(Xmalloc.data), sb);
			// Make the input argument to the main (a pointer to the malloc'ed
			// data)
			LvalExpression Xptr = main_s.addVar(input.name.identifier, inPtr_t,
					false);
			addStatement(asId(Xptr, Xmalloc.ptr), sb);

			// Create output variables:
			// Make the return value
			LvalExpression Yreturn = main_s.addVar("__" + nameOfMain + "__",
					getCType(main_d.returnType, main_d.functionNameAndArgs),
					true);

			// Allocate space for the output struct
			PointerType outPtr_t = ((PointerType) getCType(output.type,
					output.name));
			Malloc Ymalloc = malloc(outPtr_t.getPointedToType(), true);
			// Zero out output struct
			LvalExpression Ydata = Ymalloc.data;
			addStatement(asId(Ydata, IntConstant.ZERO), sb);

			// Make the output argument to the main
			LvalExpression Yptr = main_s.addVar(output.name.identifier,
					getCType(output.type, output.name), false);
			addStatement(asId(Yptr, Ymalloc.ptr), sb);

			// Initialize globals //(This strange order is so that all
			// inputStatements
			// are together, starting at output line 0)
			// addStatement(globalInit, sb);

			// Expand the call to the main
			SFE.Compiler.Expression returnValue_ = expandStatement(main_s,
					main_d.block, sb);
			if (returnValue_ == null) {
				// Default to returning 0.
				returnValue_ = VoidRetVal;
			}
			// addStatement(returnValueSts, sb);
			addStatement(asId(Yreturn, returnValue_), sb);

			// re-assign the Ydata to itself to make sure the outputs are in the
			// right
			// order.
			addStatement(asId(Ydata, Ydata), sb);

			// reassign stdout to itself to fix the numbering
			LvalExpression __stdout_buffer = Function.getVars().getVar(
					"__stdout_buffer");
			if (__stdout_buffer != null) {
				addStatement(asId(__stdout_buffer, __stdout_buffer), sb);
			}
		}
	}

	/**
	 * SLPTreduction the statement and send the result to the statement buffer.
	 */
	private static void addStatement(SFE.Compiler.Statement s,
			StatementBuffer sb) {
		s = ((SLPTReduction) s).toSLPTCircuit(null);
		// this calls toAssignmentStatements of each statement...
		s.toAssignmentStatements(sb);
	}

	/*
	 * private void setProgramWithCStyleMain(CLexicalScope main_s,
	 * FunctionDefinition main_d, SFE.Compiler.Program program){ String
	 * nameOfMain = main_d.functionNameAndArgs.identifier; Function main =
	 * functable2.get(nameOfMain); main.setOutput(true);
	 * 
	 * //Create new lexical scope for function call
	 * main_s.pushScope(nameOfMain,true);
	 * 
	 * //There should be two arguments - int argc, char** argv.
	 * VariableDeclaration argc = main_d.functionNameAndArgs.args.get(0);
	 * VariableDeclaration argv = main_d.functionNameAndArgs.args.get(1); if
	 * (!argc.type.isSubType(new TypeSpecification("int")) ||
	 * !argv.type.isSubType(new TypeSpecification("char**"))){ throw new
	 * RuntimeException
	 * ("Arguments to standard C main must have type int and char**, in that order"
	 * ); }
	 * 
	 * { //Add argc (for now, 0 args.) LvalExpression argcLval =
	 * main_s.addVar(argc.name.identifier, typetable.get("int"), false);
	 * main.addStatement(asId(argcLval, IntConstant.ZERO));
	 * 
	 * //Add argv (for now, null.) LvalExpression argvLval =
	 * main_s.addVar(argv.name.identifier, new PointerType(new
	 * PointerType(typetable.get("char"))), false);
	 * main.addStatement(asId(argvLval, IntConstant.ZERO)); }
	 * 
	 * //Make the return value if (!main_d.returnType.isSubType(new
	 * TypeSpecification("int"))){ throw new
	 * RuntimeException("Return type of standard c main must be int"); }
	 * LvalExpression Yreturn = main_s.addVar("__"+nameOfMain+"__",
	 * typetable.get("int"), true);
	 * 
	 * //Initialize globals main.addStatement(globalInit);
	 * 
	 * //Expand the call to the main BlockStatement returnValueSts = new
	 * BlockStatement(); SFE.Compiler.Expression returnValue_ =
	 * expandStatement(main_s, main_d.block, returnValueSts); if (returnValue_
	 * == null) { //Default to returning 0. returnValue_ = VoidRetVal; }
	 * main.addStatement(returnValueSts); main.addStatement(asId(Yreturn,
	 * returnValue_));
	 * 
	 * //reassign stdout to itself to fix the numbering LvalExpression
	 * __stdout_buffer = Function.getVars().getVar("__stdout_buffer"); if
	 * (__stdout_buffer != null){ main.addStatement(asId(__stdout_buffer,
	 * __stdout_buffer)); }
	 * 
	 * program.addFunction(main); }
	 */

	private void addTypeDef(Type type, Identifier name) {
		typetable.put(name.identifier, type);
	}

	private void addStructType(StructTypeSpecification q) {
		typetable.put(q.structType + " " + q.structName, getCType(q, null));
	}

	private void addFunction(FunctionDefinition fd) {
		Identifier funcid = fd.functionNameAndArgs;
		if (functable.containsKey(funcid.identifier)) {
			throw new RuntimeException("Duplicate function name: "
					+ funcid.identifier);
		}
		functable.put(funcid.identifier, fd);

		functable2.put(funcid.identifier, new Function(funcid.identifier,
				getCType(fd.returnType, null)));
	}

	private int loop_recursion = 0;

	/**
	 * Returns whatever the return value of the function is.
	 * 
	 * If there is no return statement, returns null.
	 * 
	 * @param sb
	 */
	private SFE.Compiler.Expression expandStatement(CLexicalScope scope,
			Statement statement, StatementBuffer sb) {
		if (statement instanceof CompoundStatement) {
			CompoundStatement cs = (CompoundStatement) statement;
			ArrayList<Object> s = cs.statements_declarations;
			scope.pushScope(UNNAMED_BLOCK_SCOPE_NAME);
			SFE.Compiler.Expression returnValue = null;
			for (Object s_or_d : s) {
				if (s_or_d instanceof Declaration) {
					Declaration d = (Declaration) s_or_d;
					if (d instanceof MultiVariableDeclaration) {
						MultiVariableDeclaration d2 = (MultiVariableDeclaration) d;
						for (VariableDeclaration v : d2) {
							LvalExpression var = scope.addVar(
									v.name.identifier,
									getCType(v.type, v.name), false);
						  // allocate variable address here
							// complex types' addresses are allocated on a per element basis
							var.allocateStackAddress();
							addStatement(asId(var, IntConstant.ZERO), sb);
							if (v.initializer != null) {
								addInitializerStatement(var, scope,
										v.initializer, sb);
							}
						}
					} else {
						throw new RuntimeException(
								"I don't know how to handle " + d
										+ " declaration inside of a block.");
					}
				} else if (s_or_d instanceof Statement) {
					Statement p = (Statement) s_or_d;
					returnValue = expandStatement(scope, p, sb);
					if (returnValue != null) {
						break;
					}
				} else {
					throw new RuntimeException(
							"Assertion error: statement_declaration_list held an invalid object: "
									+ s_or_d.getClass());
				}
			}
			scope.popScope();

			// No return statement.
			return returnValue;
		} else if (statement instanceof AssignmentStatement) {
			// evaluate it like an expression
			getExpr(scope, (AssignmentStatement) statement, sb);
			return null;
		} else if (statement instanceof ReturnStatement) {
			ReturnStatement rs = (ReturnStatement) statement;
			if (rs.toRet == null) {
				return VoidRetVal;
			}
			return getExpr(scope, rs.toRet, sb);
		} else if (statement instanceof Expression) {
			getExpr(scope, (Expression) statement, sb); // But disregard the
														// value
														// returned.
			return null;
		} else if (statement instanceof LoopStatement) {
			LoopStatement loop = (LoopStatement) statement;
			expandStatement(scope, loop.pre, sb);
			while (true) {
				SFE.Compiler.Expression cond = getExpr(scope, loop.enter, sb);
				FloatConstant condValue = FloatConstant.toFloatConstant(cond);

				if (condValue == null) {
					// Uncertain (either break or continue)
					loop_recursion++;
					if (loop_recursion > 1000) {
						throw new RuntimeException(
								"Infinite recursion possible; condition does not seem to be compile-time resolvable: "
										+ cond);
					}
					// Create a loop statement whose pre is the body, with the
					// same body
					// and condition.
					LoopStatement nextIteration = new LoopStatement(loop.block,
							loop.enter, loop.block, new CompoundStatement());
					IfStatement possibleNextIteration = new IfStatement(cond,
							nextIteration, new CompoundStatement());
					expandStatement(scope, possibleNextIteration, sb);
					loop_recursion--;
					break;
				} else {
					if (condValue.isZero()) {
						// Certain break
						break;
					} else {
						// Certain continue
						expandStatement(scope, loop.block, sb);
						continue;
					}
				}
			}

			/*
			 * BlockStatement loopBlock = new BlockStatement();
			 * expandStatement(scope, loop.block, loopBlock);
			 * SFE.Compiler.LoopStatement loop2 = new
			 * SFE.Compiler.LoopStatement(getExpr(scope, loop.enter, bs),
			 * loopBlock); bs.addStatement(loop2);
			 */
			return null;
		} else if (statement instanceof IfStatement) {
			// evaluate it like an expression
			getExpr(scope, (IfStatement) statement, sb);
			return null;
			/*
			 * IfStatement fib = (IfStatement)block; SFE.Compiler.Expression
			 * cond = getExpr(scope, fib.cond, bs); BlockStatement the = new
			 * BlockStatement(); scope.pushScope("then");
			 * SFE.Compiler.Expression retValThe = expandStatement(scope,
			 * fib.the, the); scope.popScope(); //If we have an else block,
			 * output it. SFE.Compiler.Expression retValEls; BlockStatement els;
			 * if (fib.els != null){ els = new BlockStatement();
			 * scope.pushScope("else"); retValEls = expandStatement(scope,
			 * fib.els, els); scope.popScope(); } else { retValEls = null; els =
			 * null; } if (retValThe != null || retValEls != null){ throw new
			 * RuntimeException(
			 * "Blocks inside an if statement cannot return values at this time."
			 * ); } SFE.Compiler.IfStatement fi = new
			 * SFE.Compiler.IfStatement(cond, the, els,
			 * ":ifstatement_"+(if_condition_uid++)); bs.addStatement(fi); //If
			 * statements cannot return at this point in time. return null;
			 */
		} else {
			throw new RuntimeException(
					"I don't know how to coerce to statement: " + statement);
		}
	}

	// private static int if_condition_uid = 0;
	// private static int return_value_uid = 0;
	private SFE.Compiler.Expression getExpr(CLexicalScope scope,
			Expression ein, StatementBuffer sb) {
		if (ein instanceof BinaryOpExpression) {
			BinaryOpExpression bo = (BinaryOpExpression) ein;
			SFE.Compiler.Expression lExpr = getExpr(scope, bo.left, sb);
			SFE.Compiler.Expression rExpr = getExpr(scope, bo.right, sb);
			// Do the types of the operands have an effect on the operator?
			SFE.Compiler.BinaryOpExpression boe = new SFE.Compiler.BinaryOpExpression(
					bo.operator, lExpr, rExpr);
			boe.metaType = CTypeForBOE(boe);
			if (bo.operator instanceof BitwiseOperator) {
				((BitwiseOperator) bo.operator)
						.setBitwiseEncoding(boe.metaType);
			}
			return boe;
		} else if (ein instanceof UnaryOpExpression) {
			UnaryOpExpression bo = (UnaryOpExpression) ein;
			// Do the types of the operands have an effect on the operator?
			SFE.Compiler.Expression mExpr = getExpr(scope, bo.middle, sb);
			SFE.Compiler.UnaryOpExpression uoe = new SFE.Compiler.UnaryOpExpression(
					bo.operator, mExpr);
			uoe.metaType = CTypeForUOE(uoe);
			if (bo.operator instanceof BitwiseOperator) {
				((BitwiseOperator) bo.operator)
						.setBitwiseEncoding(uoe.metaType);
			}
			return uoe;
		} else if (ein instanceof Constant) {
			Constant c = (Constant) ein;
			return getConstant(c, sb);
		} else if (ein instanceof Identifier) {
			Identifier identifier = (Identifier) ein;
			LvalExpression lv = Function.getVars().getVar(
					scope.getID(identifier.identifier));
			if (lv == null) {
				throw new RuntimeException("No such variable: "
						+ scope.getID(identifier.identifier));
			}
			lv.metaType = lv.getDeclaredType();
			return lv;
		} else if (ein instanceof CastExpression) {
			CastExpression cein = (CastExpression) ein;
			Type castType = getCType(cein.type, null);
			SFE.Compiler.Expression toRet = new SFE.Compiler.UnaryOpExpression(
					new CStyleCast(castType), getExpr(scope, cein.toCast, sb));
			toRet.metaType = castType;
			return toRet;
		} else if (ein instanceof MultiExpression) {
			MultiExpression me = (MultiExpression) ein;
			if (me.size() == 0) {
				throw new RuntimeException("Assertion error");
			}
			ArrayList<SFE.Compiler.Expression> exprs = new ArrayList();
			for (Expression q : me) {
				exprs.add(getExpr(scope, q, sb));
			}
			// GCC returns the result of the last expression. This may be
			// undefined
			// behavior.
			return exprs.get(exprs.size() - 1);
		} else if (ein instanceof IfStatement
				|| ein instanceof ConditionalExpression) {
			SFE.Compiler.Expression cond;
			Statement then_;
			Statement else_;
			LvalExpression muxLval;
			Type thenret_metatype = null, elseret_metatype = null;

			int parse_uid = 0;
			if (ein instanceof IfStatement) {
				IfStatement cein = (IfStatement) ein;
				parse_uid = cein.parse_uid;
				if (cein.cond == null) {
					cond = cein.cond_override;
				} else {
					cond = getExpr(scope, cein.cond, sb);
				}
				then_ = cein.the;
				else_ = cein.els;
				muxLval = null;
			} else {
				ConditionalExpression cein = (ConditionalExpression) ein;
				parse_uid = cein.parse_uid;
				cond = getExpr(scope, cein.cond, sb);
				then_ = cein.the;
				else_ = cein.els;
				muxLval = scope.addVar(":conditional_exp_" + (parse_uid),
						new BusType(), false);
				addStatement(asId(muxLval, IntConstant.ZERO), sb);
			}

			// Evaluate condition to an lvalue
			LvalExpression cond_boolean = scope.addVar(":condition"
					+ (parse_uid), new BooleanType(), false);
			addStatement(
					asId(cond_boolean, new SFE.Compiler.BinaryOpExpression(
							new NotEqualOperator(), cond, new BooleanConstant(
									false))), sb);
			cond_boolean = cond_boolean.changeReference(Function.getVars());

			BooleanConstant asConst = BooleanConstant
					.toBooleanConstant(cond_boolean);

			sb.pushUncertainty();

			Function.pushScope();

			scope.pushScope("then");
			sb.pushCondition(cond_boolean, true);
			if (asConst == null || asConst.getConst()) {
				SFE.Compiler.Expression retValThe;
				if (ein instanceof IfStatement) {
					retValThe = expandStatement(scope, then_, sb);
					if (retValThe != null) {
						throw new RuntimeException(
								"Blocks inside an if statement cannot return values at this time.");
					}
				} else {
					retValThe = getExpr(scope, (Expression) then_, sb);
					thenret_metatype = retValThe.metaType;
					addStatement(asId(muxLval, retValThe), sb);
				}
			}
			sb.popCondition();
			scope.popScope();
			Map<String, LvalExpression> thenScope = Function.popScope();

			Function.pushScope();
			scope.pushScope("else");
			sb.pushCondition(cond_boolean, false);

			if (asConst == null || !asConst.getConst()) {
				SFE.Compiler.Expression retValEls;
				if (ein instanceof IfStatement) {
					retValEls = expandStatement(scope, else_, sb);
					if (retValEls != null) {
						throw new RuntimeException(
								"Blocks inside an if statement cannot return values at this time.");
					}
				} else {
					retValEls = getExpr(scope, (Expression) else_, sb);
					elseret_metatype = retValEls.metaType;
					addStatement(asId(muxLval, retValEls), sb);
				}
			}

			sb.popCondition();
			scope.popScope();
			Map<String, LvalExpression> elseScope = Function.popScope();

			sb.popUncertainty();

			SFE.Compiler.IfStatement.expandMuxStatements(cond_boolean,
					thenScope, elseScope, sb);

			// SFE.Compiler.IfStatement fi = new SFE.Compiler.IfStatement(cond,
			// the,
			// els, ":ifstatement_"+(if_condition_uid++));
			// bs.addStatement(fi);
			if (ein instanceof IfStatement) {
				return null; // If statements return nothing.
			} else {
				muxLval = muxLval.changeReference(Function.getVars()); // This
																		// is
																		// safe,
																		// muxLval
																		// is an
																		// lvalexpression.

				if (asConst != null) {
					if (asConst.getConst()) {
						muxLval.metaType = thenret_metatype;
					} else {
						muxLval.metaType = elseret_metatype;
					}
				} else {
					muxLval.metaType = CTypeForBOE(thenret_metatype,
							new MinusOperator(), elseret_metatype);
				}
				return muxLval;
			}
			/*
			 * } else if (ein instanceof ConditionalExpression){
			 * ConditionalExpression cein = (ConditionalExpression)ein;
			 * 
			 * SFE.Compiler.Expression cond = getExpr(scope, cein.cond, sb);
			 * LvalExpression muxLval =
			 * scope.addVar(":conditional_exp_"+(if_condition_uid++), new
			 * BusType(), false);
			 * 
			 * BlockStatement the = new BlockStatement();
			 * scope.pushScope("then"); SFE.Compiler.Expression theRet =
			 * getExpr(scope, cein.the, the); scope.popScope();
			 * 
			 * BlockStatement els = new BlockStatement();
			 * scope.pushScope("else"); SFE.Compiler.Expression elsRet =
			 * getExpr(scope, cein.els, els); scope.popScope();
			 * 
			 * Type unionMetaType = CTypeForBOE(theRet.metaType, new
			 * MinusOperator(), elsRet.metaType); muxLval.metaType =
			 * unionMetaType;
			 * 
			 * //Add to the an AS of the expression value to the mux lval
			 * the.addStatement(asId(muxLval, theRet)); //Add to els an AS of
			 * the expression value to the mux lval
			 * els.addStatement(asId(muxLval, elsRet));
			 * 
			 * SFE.Compiler.IfStatement fi = new SFE.Compiler.IfStatement(cond,
			 * the, els, ":ifstatement_"+(if_condition_uid++));
			 * bs.addStatement(fi); return muxLval;
			 */
		} else if (ein instanceof FunctionCall) {
			FunctionCall fc = (FunctionCall) ein;

			ArrayList<SFE.Compiler.Expression> inputExprs = new ArrayList();
			for (Expression q : fc.args) {
				inputExprs.add(getExpr(scope, q, sb));
			}

			return genericFunctionCall(scope, fc.funcName.identifier,
					fc.parse_uid, sb, inputExprs);
		} else if (ein instanceof AssignmentStatement) {
			AssignmentStatement ast = (AssignmentStatement) ein;
			if (ast.LHS_lookup == null) {
				throw new RuntimeException(
						"Not yet implemented: AssignmentStatement as expression without LHS lookup");
			}
			SFE.Compiler.Expression lhsLookup = getExpr(scope, ast.LHS_lookup,
					sb);

			CStyleCast cc = cCastForAssignment(lhsLookup);

			// Make the assignment
			SFE.Compiler.Expression rhs = getExpr(scope, ast.RHS, sb);
			if (!(rhs instanceof OperationExpression)) {
				SFE.Compiler.Expression newRhs = new SFE.Compiler.UnaryOpExpression(
						new UnaryPlusOperator(), rhs);
				newRhs.metaType = rhs.metaType;
				rhs = newRhs;
			}
			if (ast.binaryOperator != null) {
				// What would be the metatype of performing the binary operator
				// now?
				// Make sure this check doesn't add new statements.
				SFE.Compiler.BinaryOpExpression boe = new SFE.Compiler.BinaryOpExpression(
						ast.binaryOperator, lhsLookup, rhs);
				boe.metaType = CTypeForBOE(boe);
				if (ast.binaryOperator instanceof BitwiseOperator) {
					((BitwiseOperator) ast.binaryOperator)
							.setBitwiseEncoding(boe.metaType);
				}
				ProtoAssignmentStatement newState = new ProtoAssignmentStatement(
						lhsLookup, rhs, ast.binaryOperator, cc);
				addStatement(newState, sb);
				return newState.getResolvedLHS();
			} else {
				ProtoAssignmentStatement newState = new ProtoAssignmentStatement(
						lhsLookup, rhs, cc);
				addStatement(newState, sb);
				return newState.getResolvedLHS();
			}
		} else {
			throw new RuntimeException(
					"I don't know how to coerce to expression: " + ein);
		}
	}

	private SFE.Compiler.Expression genericFunctionCall(CLexicalScope scope,
			String funcName, int parse_uid, StatementBuffer sb,
			ArrayList<SFE.Compiler.Expression> inputExprs) {
		SFE.Compiler.Expression asBuiltIn = builtinFunctionCall(scope,
				funcName, parse_uid, sb, inputExprs);
		if (asBuiltIn != null) {
			return asBuiltIn;
		}

		FunctionDefinition toCall = functable.get(funcName);
		if (toCall == null) {
			throw new RuntimeException("No function definition for " + funcName);
		}

		Identifier toCallInfo = toCall.functionNameAndArgs;

		if (inputExprs.size() != toCallInfo.args.size()) {
			throw new RuntimeException("Wrong number of arguments to function "
					+ funcName);
		}

		// Create new function scope for our call
		// Initialize the inputs to the function
		scope.pushScope(funcName, true);
		for (int i = 0; i < toCallInfo.args.size(); i++) {
			VariableDeclaration vd = toCallInfo.args.get(i);
			Type typeOfArgument = getCType(vd.type, vd.name);
			if (typeOfArgument instanceof ArrayType) {
				ArrayType typeOfArgument_ = (ArrayType) typeOfArgument;
				if (typeOfArgument_.getComponentType() instanceof ArrayType) {
					throw new RuntimeException(
							"Multidimensional arrays as arguments to function "
									+ funcName + " not yet supported");
				} else {
					// Replace typeOfArgument with pointer-to-basetype
					typeOfArgument = new PointerType(
							typeOfArgument_.getComponentType());
				}
			}
			LvalExpression addVar = scope.addVar(vd.name.identifier,
					typeOfArgument, false);
			addStatement(asId(addVar, inputExprs.get(i)), sb);
		}

		SFE.Compiler.Expression returnValue_ = expandStatement(scope,
				toCall.block, sb);
		scope.popScope();
		if (returnValue_ == null) {
			return null;
		}
		// Evaluate the returnValue to a variable in the outer scope
		BlockStatement result = new BlockStatement();
		SFE.Compiler.Expression returnValue = returnValue_.evaluateExpression(
				":" + funcName + (parse_uid), "returnvalue", result);
		result.toAssignmentStatements(sb);
		return returnValue;
	}

	private CStyleCast cCastForAssignment(SFE.Compiler.Expression lhsLookup) {
		if (lhsLookup.metaType == null) {
			return null; // No info.
		}
		CStyleCast cc;
		if (lhsLookup.metaType instanceof ArrayType) {
			cc = null; // No conversion occurs on assigning arrays to arrays
		} else if (lhsLookup.metaType instanceof StructType) {
			cc = null; // No conversion occurs on assigning structs to structs.
		} else if (lhsLookup.metaType instanceof PointerType) {
			cc = new CStyleCast(lhsLookup.metaType); // Handle assigning arrays
														// to
														// pointers.
		} else {
			// C implicit type conversion
			if (cstdarithtruncate) {
				cc = new CStyleCast(lhsLookup.metaType);
			} else {
				cc = null; // No implicit type conversion (?)
			}
		}
		return cc;
	}

    private void exoCompGetOutputVars(LvalExpression outStrP, List<LvalExpression> outVars, Map<String, StructAccessOperator> saoMap, ArrayAccessOperator aao, StatementBuffer sb) {
        if ( outStrP.getType() instanceof PointerType ) {
            throw new RuntimeException("EXO_COMPUTE output data must be concrete types, not pointers.");
        }
        else if ( outStrP.getType() instanceof ArrayType ) {
            // an array of things to add to the output variables list
            final int outLen = ((ArrayType) outStrP.getType()).getLength();
            for (int i=0; i<outLen; i++) {
                // recursive call on each member of the array
                exoCompGetOutputVars(aao.resolve(outStrP,new IntConstant(i)), outVars, saoMap, aao, sb);
            }
        } else if ( outStrP.getType() instanceof StructType ) {
            // first, build the set of struct access operators we need for this struct
            final StructType stT = (StructType) outStrP.getType();
            final List<String> sFields = stT.getFields();
            StructAccessOperator sao;
            for (int i=0; i<sFields.size(); i++) {
                // now recursively call on each element of the struct
                // cache the StructAccessOperators so we don't have to create and destroy a billion of them
                if ((sao = saoMap.get(sFields.get(i))) == null) {
                    sao = new StructAccessOperator(sFields.get(i));
                    saoMap.put(sFields.get(i),sao);
                }
                exoCompGetOutputVars(sao.resolve(outStrP), outVars, saoMap, aao, sb);
            }
        } else if ( outStrP.getType() instanceof SFE.Compiler.ScalarType ) {
            addStatement(new InputStatement(outStrP), sb);
            outVars.add(outStrP);
        } else {
            throw new RuntimeException("EXO_COMPUTE cannot interpret output variable of type " + outStrP.metaType.toString());
        }
    }

	private SFE.Compiler.Expression builtinFunctionCall(CLexicalScope scope,
			String funcName, int parse_uid, StatementBuffer sb,
			ArrayList<SFE.Compiler.Expression> args) {

        if (funcName.equals(CBuiltinFunctions.EXO_COMPUTE_NAME)) {
            // exo_compute(void *inputs[],uint32_t inSizes[], someStruct *output,int cmpNum);
            // >> input arrays and output array must be ArrayType <<

            // we do a lot of checking here.
            // in principle this could be done in the constructor for the ExoComputeStatement,
            // but it seems that in the case of the other builtin functions it's done here instead.
            // We'll stick with that seeming convention.
            if (args.size() != 4) {
                throw new RuntimeException("Number of arguments to EXO_COMPUTE must be 4.");
            }
            // now let's have a look at these here inputs
            final SFE.Compiler.Expression inLists = args.get(0);
            final SFE.Compiler.Expression inLLens = args.get(1);
            final SFE.Compiler.Expression outStrP = args.get(2);
            final SFE.Compiler.Expression eCmpNum = args.get(3);

            if (!(inLists.metaType instanceof ArrayType)) {
                throw new RuntimeException(
                        "EXO_COMPUTE first arg must be a statically allocated array " + 
                        "of pointers to lists of input for the exogenous computation.");
            }
            if (! (inLLens.metaType instanceof ArrayType)) {
                throw new RuntimeException(
                        "EXO_COMPUTE second arg must be a statically allocated array " + 
                        "of lengths for the input lists.");
            }

            final ArrayType atInL = (ArrayType) inLists.metaType;
            final ArrayType atLnL = (ArrayType) inLLens.metaType;
            if (atLnL.getLength() != atInL.getLength()) {
                throw new RuntimeException(
                        "EXO_COMPUTE first and second args must be arrays of the same " +
                        "length! First are arg lists, second are lengths of arg lists.");
            }

            // check the first arguments and save off their contents for creating the ExoComputeStatement instance
            final List<List<LvalExpression>> inVars = new ArrayList<List<LvalExpression>>(atInL.getLength());
            final ArrayAccessOperator aao = new ArrayAccessOperator();
            for (int i=0;i<atInL.getLength();i++) {
                final IntConstant idx = new IntConstant(i);

                // get the ith LvalExpression in the 1st arg, which should be a pointer
                final LvalExpression lPtr = aao.resolve((LvalExpression) inLists,idx);
                if (!(lPtr.getType() instanceof PointerType)) {
                    throw new RuntimeException(
                            "EXO_COMPUTE first arg must be an array of pointers to input lists.");
                }

                // input list length for the ith input list
                final FloatConstant aFlt = FloatConstant.toFloatConstant(
                        aao.resolve((LvalExpression) inLLens,idx));
                final BigInteger aNum = aFlt.getNumerator();
                final BigInteger aDen = aFlt.getDenominator();
                final int tmpLen = aNum.divide(aDen).intValue();

                final List<LvalExpression> tmpList = new ArrayList<LvalExpression>(tmpLen);
                inVars.add(tmpList);

                final PointerAccessOperator pao = new PointerAccessOperator();
                // iterate over length of list, resolving and adding to input list
                for (int j=0;j<tmpLen;j++) {
                    final LvalExpression pVal = pao.resolve(
                                new SFE.Compiler.BinaryOpExpression(new PlusOperator(),lPtr,IntConstant.valueOf(j)));
                    //System.out.println(pVal.getName().toString());
                    tmpList.add(pVal);
                }
            }
            
            // check third argument type: static array of structs
            if (outStrP.metaType instanceof PointerType) {
                throw new RuntimeException(
                        "EXO_COMPUTE third arg must be statically allocated storage " +
                        "for holding data from the exogenous compuation.");
            }
            final List<LvalExpression> outVars = new ArrayList<LvalExpression>();
            final Map<String, StructAccessOperator> saoMap = new HashMap<String, StructAccessOperator>();

            // get the output variables, recursively expanding the structure of each element
            exoCompGetOutputVars((LvalExpression) SFE.Compiler.Expression.fullyResolve(outStrP), outVars, saoMap, aao, sb);

            // get the exoId so that we can set the scope name appropriately
            // exogenous computation number, that is, the identifier for the
            // backend which code to run on this input (we provide executables
            // named exo0, exo1, etc.)
            final FloatConstant fcCmp = FloatConstant.toFloatConstant(
                    SFE.Compiler.Expression.fullyResolve(eCmpNum));
            final BigInteger denCmp = fcCmp.getDenominator();
            final BigInteger numCmp = fcCmp.getNumerator();
            final int exoId = numCmp.divide(denCmp).intValue();

            /* begin test code
            // print some information about the arguments
            System.out.print(Integer.toString(atInL.getLength()) + " " + atInL.getComponentType() + " ");
            System.out.println(((LvalExpression)inLists).fieldEltAt(0));
            System.out.print(SFE.Compiler.Expression.fullyResolve(inLLens).getClass().getName().toString() + " ");
            System.out.println(inLLens.metaType.toString());
            System.out.print(SFE.Compiler.Expression.fullyResolve(outStrP).getClass().getName().toString() + " ");
            System.out.println(outStrP.getType().toString());
            // end test code */

            // don't do this: we only need to create variables for the outputs, and we've done that already above
            /* create output variables
            final ArrayType atOut = (ArrayType) outStrP.metaType;
            final LvalExpression inListsVar = scope.addVar("exo:inLists",new ArrayType(atInL.getComponentType(),atInL.getLength()),false);
            final LvalExpression inLLensVar = scope.addVar("exo:inLLens",new ArrayType(atLnL.getComponentType(),atLnL.getLength()),false);
            final LvalExpression outStrPVar = scope.addVar("exo:outStrP",new ArrayType(atOut.getComponentType(),atOut.getLength()),false);
            final LvalExpression eCmpNumVar = scope.addVar("exo:eCmpNum",new IntType(),false);
            addStatement(asId(inListsVar,inLists),sb);
            addStatement(asId(inLLensVar,inLLens),sb);
            addStatement(asId(outStrPVar,outStrP),sb);
            addStatement(asId(eCmpNumVar,IntConstant.valueOf(exoId)),sb);
            // end assign input variables */

            // now we have everything we need to create the ExoComputeStatement
            addStatement(new ExoComputeStatement(inVars,outVars,exoId), sb);

            return VoidRetVal;
        }

		// fast_ram implementation.
		if (funcName.equals(CBuiltinFunctions.RAMGET_ENHANCED_NAME)) {
			scope.pushScope(CBuiltinFunctions.RAMGET_ENHANCED_NAME, true);
			if (args.size() != 2) {
				throw new RuntimeException(
						"Number of arguments to RAMGET_FAST needs to be 2.");
			}
			// Initialize the inputs to the function
			LvalExpression addrVar = scope.addVar("ram:addr", new IntType(), false);
			addStatement(asId(addrVar, args.get(1)), sb);

			// make sure args.get(1) is actually a variable.
			SFE.Compiler.Expression target = args.get(0);
			if (!(target.metaType instanceof PointerType)) {
				throw new RuntimeException(
						"Pointer to data to get in RAMGET_FAST, " + target
								+ " was not a pointer.");
			}
			
			Pointer ptr = Pointer.toPointerConstant(target);
			LvalExpression valueVar = ptr.access();
			// LvalExpression valueVar = LvalExpression.toLvalExpression(value);
			if (valueVar == null) {
				throw new RuntimeException("Cannot resolve pointer. " + target);
			}

			addStatement(new RamGetEnhancedStatement(valueVar, addrVar), sb);
			scope.popScope();
			return VoidRetVal;
		}

		if (funcName.equals(CBuiltinFunctions.RAMPUT_ENHANCED_NAME)) {
			scope.pushScope(CBuiltinFunctions.RAMPUT_ENHANCED_NAME, true);
			if (args.size() != 2) {
				throw new RuntimeException(
						"Number of arguments to RAMGET_FAST needs to be 2.");
			}

			// Initialize the inputs to the function
			LvalExpression addrVar = scope.addVar("addr", new IntType(), false);
			addStatement(asId(addrVar, args.get(0)), sb);

			LvalExpression valueVar = scope.addVar("value", new IntType(),
					false);
			addStatement(asId(valueVar, args.get(1)), sb);

			addStatement(new RamPutEnhancedStatement(addrVar, valueVar), sb);
			scope.popScope();
			return VoidRetVal;
		}

		if (funcName.equals(CBuiltinFunctions.RAMGET_NAME)) {
			// get(pointerToTarget, address);

			// Create new function scope for our call
			scope.pushScope(CBuiltinFunctions.RAMGET_NAME, true);
			// Initialize the inputs to the function

			SFE.Compiler.Expression pointerToTargetArg = args.get(0);
			if (!(pointerToTargetArg.metaType instanceof PointerType)) {
				throw new RuntimeException("Pointer to data to get in ramget, "
						+ pointerToTargetArg + " was not a pointer.");
			}
			LvalExpression ptrToTarget = scope.addVar("ptrToTarget",
					pointerToTargetArg.metaType, false);
			addStatement(asId(ptrToTarget, pointerToTargetArg), sb);

			ArrayList<LvalExpression> addrs = new ArrayList();
			for (int i = 1; i < args.size(); i++) {
				LvalExpression addVar = scope.addVar("addr" + (i - 1),
						new IntType(), false);
				addStatement(asId(addVar, args.get(i)), sb);
				addrs.add(addVar);
			}

			addStatement(new RamGetStatement(ptrToTarget, addrs), sb);
			scope.popScope();

			return VoidRetVal;
		}
		if (funcName.equals(CBuiltinFunctions.RAMPUT_NAME)) {
			// Put(address, pointer to data).

			// Create new function scope for our call
			scope.pushScope(CBuiltinFunctions.RAMPUT_NAME, true);
			// Initialize the inputs to the function
			ArrayList<LvalExpression> addrs = new ArrayList();
			for (int i = 0; i < args.size() - 1; i++) {
				LvalExpression addVar = scope.addVar("addr" + i, new IntType(),
						false);
				addStatement(asId(addVar, args.get(i)), sb);
				addrs.add(addVar);
			}
			SFE.Compiler.Expression pointerToValArg = args.get(args.size() - 1);
			if (!(pointerToValArg.metaType instanceof PointerType)) {
				throw new RuntimeException("Pointer to data to put in ramput, "
						+ pointerToValArg + " was not a pointer.");
			}
			LvalExpression pointerToValue = scope.addVar("ptrToData",
					pointerToValArg.metaType, false);
			addStatement(asId(pointerToValue, pointerToValArg), sb);

			addStatement(new RamPutStatement(addrs, pointerToValue), sb);
			scope.popScope();
			return VoidRetVal;
		}

		if (funcName.equals(CBuiltinFunctions.HASHPUT_NAME)) {
			requireHashT();
			// hashput(pointer-to-receiver-of-hash, pointer-to-object-to-hash).

			// Create new function scope for our call
			scope.pushScope(CBuiltinFunctions.HASHPUT_NAME, true);
			// Initialize the inputs to the function
			LvalExpression pointerToReceiveHash = scope.addVar("ptrToRecvHash",
					new PointerType(typetable.get("hash_t")), false);
			if (!(args.get(1).metaType instanceof PointerType)) {
				throw new RuntimeException("Second argument to hashput, "
						+ args.get(1) + " was not a pointer.");
			}
			LvalExpression pointerToValue = scope.addVar("ptrToData",
					new PointerType(new AnyType()), false);
			addStatement(asId(pointerToReceiveHash, args.get(0)), sb);
			addStatement(asId(pointerToValue, args.get(1)), sb);

			addStatement(new HashPutStatement(pointerToReceiveHash,
					pointerToValue), sb);
			scope.popScope();
			return VoidRetVal;
		}

		if (funcName.equals(CBuiltinFunctions.HASHGET_NAME)) {
			requireHashT();
			// hashget(pointer-to-receiver-of-object, pointer-to-hash).

			// Create new function scope for our call
			scope.pushScope(CBuiltinFunctions.HASHGET_NAME, true);
			// Initialize the inputs to the function
			SFE.Compiler.Expression pointerToRecvDataArg = args.get(0);
			if (!(pointerToRecvDataArg.metaType instanceof PointerType)) {
				throw new RuntimeException("First argument to hashget, "
						+ pointerToRecvDataArg + " was not a pointer.");
			}
			LvalExpression pointerToRecvData = scope.addVar("ptrToRecvDat",
					new PointerType(new AnyType()), false);
			LvalExpression pointerToHash = scope.addVar("ptrToHash",
					new PointerType(typetable.get("hash_t")), false);
			addStatement(asId(pointerToRecvData, pointerToRecvDataArg), sb);
			addStatement(asId(pointerToHash, args.get(1)), sb);

			addStatement(
					new HashGetStatement(pointerToRecvData, pointerToHash), sb);
			scope.popScope();
			return VoidRetVal;
		}

		if (funcName.equals(CBuiltinFunctions.HASHFREE_NAME)) {
			requireHashT();
			// hashfree(pointer-to-hash).

			// Create new function scope for our call
			scope.pushScope(CBuiltinFunctions.HASHFREE_NAME, true);
			// Initialize the inputs to the function
			SFE.Compiler.Expression pointerToHashArg = args.get(0);
			if (!(pointerToHashArg.metaType instanceof PointerType)) {
				throw new RuntimeException("First argument to hashfree, "
						+ pointerToHashArg + " was not a pointer.");
			}
			LvalExpression pointerToHash = scope.addVar("ptrToHash",
					new PointerType(typetable.get("hash_t")), false);
			addStatement(asId(pointerToHash, pointerToHashArg), sb);
			// Create new function scope for our call
			addStatement(new HashFreeStatement(pointerToHash), sb);
			scope.popScope();
			return VoidRetVal;
		}

		if (funcName.equals(CBuiltinFunctions.ASSERT_ZERO_NAME)) {
			// Create new function scope for our call
			scope.pushScope(CBuiltinFunctions.ASSERT_ZERO_NAME, true);
			// Initialize the inputs to the function
			LvalExpression num = scope.addVar("num", new IntType(), false);
			addStatement(asId(num, args.get(0)), sb);

			addStatement(new AssertZeroStatement(num), sb);
			scope.popScope();
			return VoidRetVal;
		}

		if (funcName.equals(CBuiltinFunctions.INT_STRING_TO_BITS)) {
			// Initialize the inputs to the function
			Pointer output = Pointer.toPointerConstant(args.get(0));
			Pointer string = Pointer.toPointerConstant(args.get(1));
			int radix = IntConstant.toIntConstant(args.get(2)).toInt();

			// First form the java.lang.String of the integer
			StringBuffer buf = new StringBuffer();
			int index = 0;
			while (true) {
				char char_val = (char) IntConstant.toIntConstant(
						string.increment(index++).access()).toInt();
				if (char_val == 0) {
					break;
				}
				buf.append(char_val);
			}
			IntConstant parsed = IntConstant.valueOf(new BigInteger(buf
					.toString(), radix));

			BitStringHelper.BitStringStoreTarget bsst = BitStringHelper
					.toBitStringStoreTarget(output);
			BitString bitsToRecvDat = bsst.targetBits;
			LvalExpression target = bsst.targetCVar;
			// subStatements = bsst.dummyAssignmentsToTargetBits;
			BitString parsed_bs = BitString.toBitString(
					new RestrictedSignedIntType(bitsToRecvDat.size()), sb,
					parsed);

			// Bitstring assignments.
			parsed_bs.toAssignments(target, sb);

			return VoidRetVal;
		}

		if (funcName.equals(CBuiltinFunctions.COMMITMENTGET_NAME)) {
			requireHashT();
			// hashget(pointer-to-receiver-of-object, pointer-to-hash).

			// Create new function scope for our call
			scope.pushScope(CBuiltinFunctions.COMMITMENTGET_NAME, true);
			// Initialize the inputs to the function
			SFE.Compiler.Expression pointerToRecvDataArg = args.get(0);
			if (!(pointerToRecvDataArg.metaType instanceof PointerType)) {
				throw new RuntimeException("First argument to "
						+ CBuiltinFunctions.COMMITMENTGET_NAME + ", "
						+ pointerToRecvDataArg + " was not a pointer.");
			}
			LvalExpression pointerToRecvData = scope.addVar("ptrToRecvDat",
					new PointerType(new AnyType()), false);
			LvalExpression pointerToHash = scope.addVar("ptrToHash",
					new PointerType(typetable.get("commitment_t")), false);
			addStatement(asId(pointerToRecvData, pointerToRecvDataArg), sb);
			addStatement(asId(pointerToHash, args.get(1)), sb);
			pointerToRecvData = pointerToRecvData.changeReference(Function
					.getVars());
			pointerToHash = pointerToHash.changeReference(Function.getVars());

			int NUM_COMMITMENT_BITS_PER_BLOCK = IntConstant.toIntConstant(
					Consts.fromName("NUM_COMMITMENT_BITS_PER_BLOCK")).toInt();
			// Data to store into
			BitStringStoreTarget bitsData = BitStringHelper
					.toBitStringStoreTarget(pointerToRecvData);
			int numBitsInMessage = bitsData.targetBits.size();

			if (NUM_COMMITMENT_BITS_PER_BLOCK % 8 != 0
					|| numBitsInMessage % 8 != 0) {
				throw new RuntimeException(
						"Sizes not a multiple of 8: Commitment bits = "
								+ NUM_COMMITMENT_BITS_PER_BLOCK
								+ ", message length in bits = "
								+ numBitsInMessage);
			}

			StructType committed_state_t = new StructType();
			committed_state_t.addField("secret", new ArrayType(
					new RestrictedUnsignedIntType(8),
					NUM_COMMITMENT_BITS_PER_BLOCK / 8));
			committed_state_t.addField("message", new ArrayType(
					new RestrictedUnsignedIntType(8), numBitsInMessage / 8));

			// Data to get (secret || data)
			Malloc committed_state = malloc(committed_state_t, false);
			addStatement(asId(committed_state.data, IntConstant.ZERO), sb);

			addStatement(new GenericGetStatement(
					GenericGetStatement.COMMITMENT_LOOKUP, committed_state.ptr,
					pointerToHash), sb);
			committed_state.data = committed_state.data
					.changeReference(Function.getVars());

			// Holder for computed hash
			Malloc computed_hash = malloc(typetable.get("commitment_t"), false);
			addStatement(asId(computed_hash.data, IntConstant.ZERO), sb);

			// Synthesize a function call and evaluate it
			LvalExpression committed_data = LvalExpression
					.toLvalExpression(new SFE.Compiler.UnaryOpExpression(
							new StructAccessOperator("message"),
							committed_state.data));
			LvalExpression secret = LvalExpression
					.toLvalExpression(new SFE.Compiler.UnaryOpExpression(
							new StructAccessOperator("secret"),
							committed_state.data));

			ArrayList<SFE.Compiler.Expression> args_to_commitment_hash = new ArrayList();
			// uint8_t* message, int num_bits_message, uint8_t* key,
			// commitment_t*
			// hash
			args_to_commitment_hash.add(committed_data);
			args_to_commitment_hash.add(IntConstant.valueOf(numBitsInMessage));
			args_to_commitment_hash.add(secret);
			args_to_commitment_hash.add(computed_hash.ptr);
			genericFunctionCall(scope, "commitment_hash", parse_uid, sb,
					args_to_commitment_hash);
			computed_hash.data = computed_hash.data.changeReference(Function
					.getVars());

			// Committed hash
			LvalExpression commitment_hash = LvalExpression
					.toLvalExpression(new SFE.Compiler.UnaryOpExpression(
							new PointerAccessOperator(), pointerToHash));
			// compare commitment_hash against computed_hash
			// and emit a bunch of "assertZero's"

			for (int i = 0; i < commitment_hash.size(); i++) {
				addStatement(
						new AssertZeroStatement(
								new SFE.Compiler.BinaryOpExpression(
										new PlusOperator(),
										new SFE.Compiler.BinaryOpExpression(
												new TimesOperator(),
												commitment_hash.fieldEltAt(i),
												IntConstant.NEG_ONE),
										computed_hash.data.fieldEltAt(i))), sb);
			}

			// Reassign committed_state's data to bitsData
			// The first type is the type of commited_data, the second type is
			// that of
			// the store target.
			// raw bitstring is the intermediate
			BitString.toBitString(committed_data.getDeclaredType(), sb,
					committed_data).toAssignments(bitsData.targetCVar, sb);

			scope.popScope();
			return VoidRetVal;
		}

		if (funcName.equals(CBuiltinFunctions.COMMITMENTPUT_NAME)) {
			throw new RuntimeException("Not yet implemented");
		}

		if (funcName.equals("printf")) {
			addStatement(new PrintfStatement(args), sb);
			return VoidRetVal;
		}

		return null;
	}

	private void requireHashT() {
		if (!typetable.containsKey("hash_t")) {
			throw new RuntimeException(
					"Add #include<db.h> to use built-in hashing and commitment functions");
		}
	}

	private Type CTypeForUOE(SFE.Compiler.UnaryOpExpression uoe) {
		Type middle = uoe.getMiddle().metaType;

		if (uoe.op instanceof PointerAccessOperator) {
			return getPointedToType(middle);
		} else if (uoe.op instanceof StructAccessOperator) {
			if (!(middle instanceof StructType)) {
				throw new RuntimeException("Could not " + uoe.op.toString()
						+ " on " + uoe.getMiddle() + ", which is not a struct.");
			}
			StructType st = (StructType) middle;
			return st.fromFieldName(((StructAccessOperator) uoe.op).getField());
		} else if (uoe.op instanceof GetPointerOperator) {
			return new PointerType(middle);
		} else if (middle instanceof IntType) {
			// Only handle the case where left and right are integers, for now.
			// Integer promotions
			middle = integerPromote(middle);

			return middle;
		} else {
			throw new RuntimeException(
					"I don't know how to form the C-type for UOE " + uoe.op
							+ " " + middle);
		}
	}

	private Type CTypeForBOE(SFE.Compiler.BinaryOpExpression boe) {
		Type left = boe.getLeft().metaType;
		Type right = boe.getRight().metaType;
		return CTypeForBOE(left, boe.op, right);
	}

	private Type getPointedToType(Type t) {
		if (t instanceof PointerType) {
			return ((PointerType) t).getPointedToType();
		} else if (t instanceof ArrayType) {
			return ((ArrayType) t).getComponentType();
		} else {
			throw new RuntimeException("I don't know how to treat type " + t
					+ " like a pointer");
		}
	}

	private boolean isPOAType(Type t) {
		return (t instanceof PointerType) || (t instanceof ArrayType);
	}

	private Type CTypeForBOE(Type left, Operator op, Type right) {
		// Integer-Integer binary ops
		if (op instanceof ArrayAccessOperator) {
			ArrayType at = (ArrayType) left;
			return at.getComponentType();
		} else if (op instanceof PlusOperator
				&& (isPOAType(left) || isPOAType(right))) {
			Type ptType = left;
			Type intType = right;
			if (!isPOAType(ptType)) {
				ptType = right;
				intType = left;
			}
			if (!(intType instanceof IntType)) {
				throw new RuntimeException("Cannot add " + intType
						+ " to a pointer");
			}
			return ptType;
		} else if (op instanceof MinusOperator && isPOAType(left)) {
			if (isPOAType(right)) {
				// We can subtract pointers, but they must have the same type.
				throw new RuntimeException(
						"Subtraction of pointers not yet implemented");
			}
			if (!(right instanceof IntType)) {
				throw new RuntimeException("Cannot subtract " + right
						+ " off of a pointer");
			}
			return left;
		} else if (left instanceof IntType && right instanceof IntType) {
			// Integer promotions
			left = integerPromote(left);
			right = integerPromote(right);

			// Usual arithmetic conversions
			// 1. If both operands have the same type, no further conversion is
			// needed.
			if (TypeHeirarchy.equals(left, right)) {
				return left;
			}
			// 2. If both operands are of the same integer type (signed or
			// unsigned),
			// the operand with the type of lesser integer conversion rank is
			// converted to the type of the operand with greater rank.
			boolean leftSigned = left instanceof RestrictedSignedIntType;
			boolean rightSigned = right instanceof RestrictedSignedIntType;
			if (leftSigned == rightSigned) {
				return TypeHeirarchy.looseUnion(left, right);
			}
			// 3. If the operand that has unsigned integer type has rank greater
			// than
			// or equal to the rank of the type of the other operand, the
			// operand with
			// signed integer type is converted to the type of the operand with
			// unsigned integer type.
			int leftBits = IntType.getBits((IntType) left);
			int rightBits = IntType.getBits((IntType) right);
			if (!leftSigned && leftBits >= rightBits) {
				return left; // Negative values of right will be added to the
								// max of
								// left plus 1.
			}
			if (!rightSigned && rightBits >= leftBits) {
				return right; // Negative values of left will be added to the
								// max of
								// left plus 1.
			}
			// 4. If the type of the operand with signed integer type can
			// represent
			// all of the values of the type of the operand with unsigned
			// integer
			// type, the operand with unsigned integer type is converted to the
			// type
			// of the operand with signed integer type.
			if (!leftSigned && TypeHeirarchy.isSubType(left, right)) {
				return right; // right is signed
			}
			if (!rightSigned && TypeHeirarchy.isSubType(right, left)) {
				return left; // left is signed
			}
			// 5. Otherwise, both operands are converted to the unsigned integer
			// type
			// corresponding to the type of the operand with signed integer
			// type.
			// Specific operations can add to or modify the semantics of the
			// usual
			// arithmetic operations.
			throw new RuntimeException(
					"I don't know how to form the C-type for operating on "
							+ left + " and " + right);
		} else {
			throw new RuntimeException(
					"I don't know how to form the C-type for BOE " + left + " "
							+ op + " " + right);
		}
	}

	private Type integerPromote(Type c) {
		// If an int can hold the op, return int
		Type i_t = typetable.get("int");
		if (TypeHeirarchy.isSubType(c, i_t)) {
			return i_t;
		}
		// If an unsigned int can hold the op, return unsigned int
		Type ui_t = typetable.get("unsigned int");
		if (TypeHeirarchy.isSubType(c, ui_t)) {
			return ui_t;
		}
		// Otherwise, the type does not need promotion
		return c;
	}

	private void builtinTypes() {

		typetable.put("void", null); // null is handled correctly for void
		for (String signString : new String[] { "", "signed", "unsigned" }) {
			typetable.put(join(signString, "char"), intType(signString, 8));
			typetable.put(join(signString, "short"), intType(signString, 16)); // "At least 16 bits in size"
			typetable.put(join(signString, "short int"),
					intType(signString, 16)); // "At least 16 bits in size"
			typetable.put(join(signString, "int"), intType(signString, 32)); // "At least 16 bits in size"
																				// -
																				// but
																				// 32
																				// is
																				// needed
																				// for
																				// compliance
																				// with
																				// the
																				// stdlib.h
																				// on
																				// my
																				// system?
			typetable.put(join(signString, "long"), intType(signString, 64)); // "At least 32 bits in size"
			typetable
					.put(join(signString, "long int"), intType(signString, 64)); // "At least 32 bits in size"
			typetable.put(join(signString, "long long"),
					intType(signString, 64)); // "At least 64 bits in size"
			typetable.put(join(signString, "long long int"),
					intType(signString, 64)); // "At least 64 bits in size"
		}

		// NONSTANDARD "bool" extension
		typetable.put("bool", intType("unsigned", 1));
	}

	private void builtinFunctions() {
		// Reserve the function names getdb and putdb.
		addFunction(new FunctionDefinition(new Identifier(
				CBuiltinFunctions.RAMGET_NAME), null));
		addFunction(new FunctionDefinition(new Identifier(
				CBuiltinFunctions.RAMPUT_NAME), null));
	}

	private int constant_string_uid = 0;

	private SFE.Compiler.Expression getConstant(Constant e, StatementBuffer sb) {
		// Prevents adding string constants multiple times to the strtable.
		if (e.const_expr == null) {
			e.const_expr = getConstant_(e, sb);
		}
		return e.const_expr;
	}

	private SFE.Compiler.Expression getConstant_(Constant e, StatementBuffer sb) {
		String val = e.value + "";
		if (e.constType.equals("string")) {
			int addressOfList = CMemoryMap.STRING_TABLE + constant_string_uid;

			String strValue = ((String) e.value);
			// strValue is wrapped in quotes
			strValue = strValue.substring(1, strValue.length() - 1);

                        if (strValue.contains("\\")){
                                System.out.println(
                                        "WARNING: Backslash in string literal"
                                        + " not treated as escape character but as"
                                        + " real backslash: " + strValue
                                );
                        }

			int N = strValue.length() + 1; // +1 for C null-terminated string
			LvalExpression[] list = new LvalExpression[N];
			Type charType = typetable.get("char");
			for (int i = 0; i < N; i++) {
				list[i] = Function.getVars().addVar(
						"__strtable" + constant_string_uid, charType, false,
						false);
				constant_string_uid++;

				SFE.Compiler.Expression char_val;
				if (i == N - 1) {
					char_val = IntConstant.ZERO;
				} else {
					char_val = IntConstant.valueOf((int) (strValue.charAt(i)));
				}

				addStatement(asId(list[i], char_val), sb);
			}

			// this is the place where list can contain multiple entries.
			Pointer toRet = new Pointer(charType, list, addressOfList,
					addressOfList);
			toRet.metaType = toRet.getType();
			return toRet;
		} else if (e.constType.equals("fp")) {
			throw new RuntimeException(
					"Floating point constants not yet handled: " + val);
		} else if (e.constType.startsWith("int")) {
			String[] possibilities;
			// Behavior based on
			// http://publib.boulder.ibm.com/infocenter/zos/v1r12/index.jsp?topic=%2Fcom.ibm.zos.r12.cbclx01%2Flit_integer.htm
			// using the table "outside of C99 and C++0x", i.e. maximum
			// compatibility
			// with old code.
			if (e.constType.contains("hex") || e.constType.contains("octal")) {
				possibilities = new String[] { "int", "unsigned int",
						"long int", "unsigned long int" };
			} else if (e.constType.contains("decimal")) {
				possibilities = new String[] { "int", "long int",
						"unsigned long int" };
			} else {
				throw new RuntimeException(
						"I don't know how to handle int of constant type: "
								+ e.constType);
			}

			String numVal;
			if (val.toLowerCase().endsWith("ull")) {
				numVal = allButLast(val, 3);
				possibilities = new String[] { "unsigned long long int" };
			} else if (val.toLowerCase().endsWith("ll")) {
				numVal = allButLast(val, 2);
				possibilities = new String[] { "long long int",
						"unsigned long long int" };
			} else if (val.toLowerCase().endsWith("ul")) {
				numVal = allButLast(val, 2);
				possibilities = new String[] { "unsigned long int" };
			} else if (val.toLowerCase().endsWith("u")) {
				numVal = allButLast(val, 1);
				possibilities = new String[] { "unsigned int",
						"unsigned long int" };
			} else if (val.toLowerCase().endsWith("l")) {
				numVal = allButLast(val, 1);
				possibilities = new String[] { "long int", "unsigned long int" };
			} else {
				numVal = val;
			}

			FloatConstant toRet = FloatConstant.valueOf(numVal);
			for (String q : possibilities) {
				Type gotType = typetable.get(q);
				if (TypeHeirarchy.isSubType(toRet.getType(), gotType)) {
					toRet.metaType = gotType;
					break; // Keep the first that works.
				}
			}
			if (toRet.metaType == null) {
				throw new RuntimeException("Integer too large to represent: "
						+ val);
			}
			return toRet;
		} else if (e.constType.equals("char")) {
			// strValue is wrapped in single quotes
                        if (val.length() != 3){
				throw new RuntimeException("Char constants must"
                                + " be a single character long, escape sequences"
                                + " not supported: "
				+ val);
                        }

                        Integer charValue = (int) val.charAt(1);

                        IntConstant toRet = IntConstant.valueOf(charValue);

			Type charType = typetable.get("char");

                        if (TypeHeirarchy.isSubType(toRet.getType(), charType)){
                                toRet.metaType = charType;
                        } else {
                                throw new RuntimeException(
                                "Assertion error: "
                                + "Char constant too large?" + val
                                );
                        }

			return toRet;
		} else {
			throw new RuntimeException(
					"I don't know how to handle const of type: " + e.constType
							+ " @ " + val);
		}
	}

	private String allButLast(String numVal, int i) {
		return numVal.substring(0, numVal.length() - i);
	}

	/**
	 * UnaryPlusOperator is used to make assignments of varying sizes. Use this
	 * instead of simply assigning one thing to another, because that rarely
	 * works.
	 */
	private SFE.Compiler.Statement asId(LvalExpression lval,
			SFE.Compiler.Expression rhs) {
		lval.metaType = lval.getDeclaredType();

		if (rhs instanceof OperationExpression
				&& !(((OperationExpression) rhs).getOperator() instanceof CompileTimeOperator)) {
			return as(lval, rhs);
		} else {
			return as(lval, new SFE.Compiler.UnaryOpExpression(
					new UnaryPlusOperator(), rhs));
		}
	}

	private SFE.Compiler.Statement as(LvalExpression lval,
			SFE.Compiler.Expression rhs) {
		// return new SFE.Compiler.AssignmentStatement(lval, rhs);
		// Use protoAssignments because they can allow assignments to varying
		// sizes
		// when pointer indirection is involved
		return new ProtoAssignmentStatement(lval, rhs, cCastForAssignment(lval));
	}

	private String join(String a, String b) {
		if (a.equals("")) {
			return b;
		}
		return a + " " + b;
	}

	private Type intType(String signString, int i) {
		if (signString.equals("unsigned")) {
			return new RestrictedUnsignedIntType(i);
		}
		return new RestrictedSignedIntType(i);
	}

	public Type getCType(TypeSpecification ts, Identifier name) {
		Type got = getCTypeBase(ts);

		// TODO: support syntax int (*a)[5], which distinguishes a pointer to an
		// array of length 5
		// from int* a[5], an array of 5 pointers to ints.

		if (name != null) {
			// the base type may be a pointer (i.e. array of pointers, or just a
			// pointer)
			if (name.pointer != null) {
				for (char c : name.pointer.spec.toCharArray()) {
					if (c == '*') {
						got = new PointerType(got);
					} else if (c == ' ') {
						continue;
					} else {
						throw new RuntimeException(
								"Unrecognized pointer character: " + c);
					}
				}
			}

			// Read dimensions of array back to front
			ArrayList<Expression> sizes = name.arraySizes;
			if (!sizes.isEmpty()) {
				for (ListIterator<Expression> itr = sizes.listIterator(sizes
						.size()); itr.hasPrevious();) {
					Expression k = itr.previous();
					got = new ArrayType(got, toInt(k));
				}
			}
		}

		if (got == null) {
			// Default int
			return typetable.get("int");
		}
		return got;
	}

	/**
	 * Type qualifiers such as static and const must be stripped off before
	 * calling this function.
	 */
	private Type getCTypeBase(TypeSpecification ts) {
		if (ts instanceof StructTypeSpecification) {
			StructType toRet = new StructType();
			StructTypeSpecification ts2 = (StructTypeSpecification) ts;
			for (VariableDeclaration v : ts2.components) {
				Type cType = getCType(v.type, v.name);
				toRet.addField(v.name.identifier, cType);
			}
			return toRet;
		}

		// Strip off words we don't handle
		String spec = ts.spec;
		while (true) {
			String[] parts = spec.split(" ", 2);
			if (parts[0].equals("static")) {
				System.out
						.println("WARNING: Compiler ignoring 'static' keyword in "
								+ ts.spec);
				spec = parts[1];
			} else if (parts[0].equals("const")) {
				System.out
						.println("WARNING: Compiler may allow multiple assignments to 'const' variable with type "
								+ ts.spec);
				spec = parts[1];
			} else if (parts[0].equals("inline")) {
				System.out
						.println("WARNING: Compiler ignoring 'inline' keyword in "
								+ ts.spec);
				spec = parts[1];
			} else {
				break;
			}
		}

		if (!typetable.containsKey(spec)) {
			throw new RuntimeException("No such type: " + ts);
		}
		return typetable.get(spec);
	}

	/**
	 * Attempts to parse k as a "constant expression" in the C terminology.
	 * Constant expressions can basically just be arithmetic expressions of
	 * integer constants or const variables which have also been assigned
	 * constant expressions.
	 */
	private int toInt(Expression k) {
		if (k instanceof Constant) {
            final String constStr = ((Constant) k).value.toString();
            if (constStr.length() >= 2) {
                switch (constStr.substring(0,2)) {
                    case "0b":
                    case "0B":
                        return Integer.parseInt(constStr.substring(2),2);
                    case "0x":
                    case "0X":
                        return Integer.parseInt(constStr.substring(2),16);
                    default:
                        if (constStr.substring(0,1).equals("0")) {
                            return Integer.parseInt(constStr.substring(1),8);
                        } else {
                            return Integer.parseInt(constStr);
                        }
                }
            } else {
                return Integer.parseInt(constStr);
            }
		} else if (k instanceof MultiExpression) {
			MultiExpression m = (MultiExpression) k;
			if (m.size() == 1) {
				return toInt(m.get(0));
			} else {
				throw new RuntimeException(
						"I don't know how to coerce multiexpression " + m
								+ " to an int.");
			}
		} else if (k instanceof UnaryOpExpression) {
			UnaryOpExpression kop = (UnaryOpExpression) k;
			if (kop.operator instanceof UnaryMinusOperator) {
				return -toInt(kop.middle);
			}
			throw new RuntimeException(
					"Constant handling of unary op expressions TODO");
		} else if (k instanceof BinaryOpExpression) {
			BinaryOpExpression kop = (BinaryOpExpression) k;
			if (kop.operator instanceof PlusOperator) {
				return toInt(kop.left) + toInt(kop.right);
			}
			if (kop.operator instanceof MinusOperator) {
				return toInt(kop.left) - toInt(kop.right);
			}
			if (kop.operator instanceof TimesOperator) {
				return toInt(kop.left) * toInt(kop.right);
			}
			if (kop.operator instanceof DivisionOperator) {
				DivisionOperator dO = (DivisionOperator) kop.operator;
				switch (dO.getMode()) {
				case DivisionOperator.REMAINDER:
					return toInt(kop.left) % toInt(kop.right);
				case DivisionOperator.QUOTIENT:
					return toInt(kop.left) / toInt(kop.right);
				}
			}
			throw new RuntimeException("Constant handling of " + kop.operator
					+ " op expressions not yet handled");
		} else if (k instanceof Identifier) {
			// Try to get k as a constant in the Consts file
			ConstExpression c = Consts.fromName(((Identifier) k).identifier);
			if (c != null) {
				IntConstant ic = IntConstant.toIntConstant(c);
				if (ic != null) {
					return ic.toInt();
				}
			}
		}

		throw new RuntimeException("I don't know how to coerce " + k
				+ " to a constant.");
	}
}
